#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Text;

namespace Osh.Teletext
{
	/// <summary>
	/// TeletextProvider class provides simplified access to TeletextService.
	/// It manages navigation between teletext pages and notifies about page updates and page changes.
	/// TeletextProvider implements ITeletextProvider interface providing scripting access to TeletextService.
	/// </summary>
	public class TeletextProvider : ITeletextProvider
	{
		private static DisplayPageAddress FallbackInitialPageAddress =
			new DisplayPageAddress(0x100, PageAddress.NullSubCodeNumber);

		private TeletextService _service;
		private TeletextPage _currentPage;
		private DisplayPageAddress _address = FallbackInitialPageAddress;
		private bool _waitingInitialPage = true;
		private List<DisplayPageAddress> _addresses = null;
		private object _addressesLock = new object();

		public TeletextProvider(TeletextService service)
		{
			if(service == null)
				throw new ArgumentNullException("service");
			_service = service;
			_service.MagazineAdd += new EventHandler<TeletextEventArgs>(TeletextService_MagazineAdd);
			_service.MagazineRemoveAll += new EventHandler<TeletextEventArgs>(TeletextService_MagazineRemoveAll);
		}

		private void TeletextService_MagazineRemoveAll(object sender, TeletextEventArgs e)
		{
			_currentPage = null;
			_address = FallbackInitialPageAddress;
			_waitingInitialPage = true;
			ClearServicePageAddresses();
		}

		private void TeletextService_MagazineAdd(object sender, TeletextEventArgs e)
		{
			e.Magazine.PageLoaded += new EventHandler<TeletextEventArgs>(TeletextMagazine_PageLoaded);
		}

		private void TeletextMagazine_PageLoaded(object sender, TeletextEventArgs e)
		{
			UpdateServicePageAddresses(e.Page.Address);

			if(_waitingInitialPage && _service.InitialPageAddress != PageAddress.Null)
			{
				GoToPage(new DisplayPageAddress(_service.InitialPageAddress));
				return;
			}

			if(_currentPage == null && e.Page.Address.IsCompatible(new PageAddress(_address)))
			{
				GoToPage(new DisplayPageAddress(e.Page.Address));
				return;
			}

			if(_currentPage == e.Page)
				OnPageUpdate();
		}

		private List<DisplayPageAddress> GetServicePageAddresses()
		{
			lock(_addressesLock)
			{
				if(_addresses == null)
				{
					List<DisplayPageAddress> list = new List<DisplayPageAddress>();
					for(int m = 0; m < _service.Magazines.Length; m++)
					{
						TeletextMagazine magazine = _service.Magazines[m];
						if(magazine == null)
							continue;

						list.AddRange(magazine.GetDisplayPageAddresses());
					}
					list.Sort();
					_addresses = list;
				}

				return new List<DisplayPageAddress>(_addresses);
			}
		}

		private void UpdateServicePageAddresses(PageAddress address)
		{
			lock(_addressesLock)
			{
				if(_addresses != null && address.CanBeDisplayed)
				{
					DisplayPageAddress dispAddress = new DisplayPageAddress(address);
					int index = _addresses.BinarySearch(dispAddress);
					if(index < 0)
						_addresses.Insert(~index, dispAddress);
				}
			}
		}

		private void ClearServicePageAddresses()
		{
			lock(_addressesLock)
				_addresses = null;
		}

		public int ClosestIndexOf(List<DisplayPageAddress> addresses, DisplayPageAddress address, bool getNext)
		{
			if(getNext)
			{
				for(int i = 0; i < addresses.Count; i++)
					if(address <= addresses[i])
						return i;
			}
			else
			{
				for(int i = addresses.Count - 1; i >= 0; i--)
					if(addresses[i] <= address)
						return i;
			}
			return -1;
		}

		#region ITeletextProvider Members
		public bool NextPage(bool skipSubPages, bool skipMissingPages)
		{
			if(skipMissingPages)
			{
				List<DisplayPageAddress> addresses = GetServicePageAddresses();
				int index = -1;
				for(int i = 0; i < addresses.Count; i++)
				{
					if(addresses[i] > _address)
					{
						if(skipSubPages)
						{
							for(; i < addresses.Count; i++)
								if(addresses[i].MagazineAndPageNumber != _address.MagazineAndPageNumber)
								{
									index = i;
									break;
								}
						}
						else
							index = i;
						break;
					}
				}

				if(index == -1)
					return false;

				return GoToPage(addresses[index]);
			}
			else
			{
				DisplayPageAddress nextAddress = _address;
				if(skipSubPages)
				{
					nextAddress = _address.ChangeMagazineAndPageNumber(1);
					nextAddress.SubCode = 1;
					TeletextPage page;
					if(!_service.TryGetPage(new PageAddress(nextAddress), out page))
						nextAddress.SubCode = 0;
				}
				else
				{
					nextAddress = _address.ChangeSubCode(1);
				}
				return GoToPage(nextAddress);
			}
		}

		public bool PreviousPage(bool skipSubPages, bool skipMissingPages)
		{
			if(skipMissingPages)
			{
				List<DisplayPageAddress> addresses = GetServicePageAddresses();

				int index = -1;
				for(int i = addresses.Count - 1; i >= 0; i--)
				{
					if(addresses[i] < _address)
					{
						if(skipSubPages)
						{
							for(; i >= 0; i--)
							{
								if(addresses[i].MagazineAndPageNumber != _address.MagazineAndPageNumber)
								{
									index = i; // Found the last subpage of the previous page

									// Let's find the first available subpage of the found page
									for(i = index - 1; i >= 0; i--)
									{
										if(addresses[i].MagazineAndPageNumber != addresses[i+1].MagazineAndPageNumber)
											break;
										index = i;
									}
									break;
								}
							}
						}
						else
							index = i;
						break;
					}
				}

				if(index == -1)
					return false;

				return GoToPage(addresses[index]);
			}
			else
			{
				DisplayPageAddress nextAddress = _address;
				if(skipSubPages)
				{
					nextAddress = _address.ChangeMagazineAndPageNumber(-1);
					nextAddress.SubCode = 1;
					TeletextPage page;
					if(!_service.TryGetPage(new PageAddress(nextAddress), out page))
						nextAddress.SubCode = 0;
				}
				else
				{
					nextAddress = _address.ChangeSubCode(-1);
				}
				return GoToPage(nextAddress);
			}
		}

		public bool GoToInitialPage()
		{
			PageAddress address = _service.InitialPageAddress;
			if(address != PageAddress.Null)
				return GoToPage(new DisplayPageAddress(address));
			
			// If no initial page is specified then temporary go to fallback page (100).
			bool result = GoToPage(FallbackInitialPageAddress);
			_waitingInitialPage = true;	// in GoToPage method this property will be reset, so set it after the call
			return result;
		}

		public bool GoToPage(int pageNumber, int subPageNumber)
		{
			if(subPageNumber == -1)
				return GoToPage(new DisplayPageAddress(
					DisplayPageAddress.ToHex(pageNumber), PageAddress.NullSubCodeNumber));
			else
				return GoToPage(new DisplayPageAddress(
					DisplayPageAddress.ToHex(pageNumber), DisplayPageAddress.ToHex(subPageNumber)));
		}

		public bool FollowLink(int linkIndex)
		{
			TeletextPage page = _currentPage;
			if(page == null)
				return false;

			PageAddress link;
			if(!page.TryGetLink(linkIndex, out link))
				return false;

			if(link == PageAddress.Null)
				return false;

			return GoToPage(new DisplayPageAddress(link));
		}

		public int PageNumber
		{
			get { return DisplayPageAddress.ToDecimal(_address.MagazineAndPageNumber); }
			set
			{
				DisplayPageAddress address = _address;
				address.MagazineAndPageNumber = DisplayPageAddress.ToHex(value);
				GoToPage(address);
			}
		}

		public int SubPageNumber
		{
			get
			{
				if(_address.SubCode == PageAddress.NullSubCodeNumber)
					return -1;
				else
					return DisplayPageAddress.ToDecimal(_address.SubCode);
			}
			set
			{
				DisplayPageAddress address = _address;
				address.SubCode = DisplayPageAddress.ToHex(value);
				GoToPage(address);
			}
		}

		public TeletextPage CurrentPage
		{
			get { return _currentPage; }
		}
		#endregion ITeletextProvider Members

		public bool GoToPage(DisplayPageAddress dispAddress)
		{
			PageAddress address = new PageAddress(dispAddress);
			if(_currentPage != null && _currentPage.Address == address)
				return true;

			_waitingInitialPage = false;

			TeletextPage page;
			if(!_service.TryGetPage(address, out page))
			{
				_address = dispAddress;	// Store address to open the page when it's available
				_currentPage = null;
				OnPageChange();
				return false;
			}

			// The page address may be different from requested if NullSubCode was used.
			_address = new DisplayPageAddress(page.Address);

			_currentPage = page;
			OnPageChange();
			return true;
		}

		protected virtual void OnPageChange()
		{
			if(this.PageChange != null)
				this.PageChange(this, new TeletextEventArgs(_service,
					_currentPage != null ?_currentPage.Magazine : null, _currentPage));
		}

		protected virtual void OnPageUpdate()
		{
			if(this.PageUpdate != null)
				this.PageUpdate(this, new TeletextEventArgs(_service,
					_currentPage != null ?_currentPage.Magazine : null, _currentPage));
		}

		public event EventHandler<TeletextEventArgs> PageChange;
		public event EventHandler<TeletextEventArgs> PageUpdate;
	}
}
